package mini_game_fx;
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

import com.sun.javafx.tk.Toolkit;
import java.io.File;
import java.net.MalformedURLException;
import java.util.Timer;
import java.util.TreeMap;
import java.util.concurrent.locks.ReentrantLock;
import javafx.animation.*;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.media.Media;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;



/**
 *
 * @author Joshua
 */
public abstract class MiniGameFx{// extends Application{
    
        
        // THIS IS THE NAME OF THE CUSTOMIZED GAME
	protected String name;
	
	// data WILL MANAGE OUR GAME DATA, WHICH SHOULD
	// BE CUSTOMIZED FOR THE GIVEN GAME
	protected MiniGameDataModelFx data;

	// WE ARE GOING TO HAVE 2 THREADS AT WORK IN THIS APPLICATION,
	// THE MAIN GUI THREAD, WHICH WILL LISTEN FOR USER INTERACTION
	// AND CALL OUR EVENT HANDLERS, AND THE TIMER THREAD, WHICH ON
	// A FIXED SCHEDULE (e.g. 30 times/second) WILL UPDATE ALL THE
	// GAME DATA AND THEN RENDER. THIS LOCK WILL MAKE SURE THAT
	// ONE THREAD DOESN'T RUIN WHAT THE OTHER IS DOING (CALLED A
	// RACE CONDITION). FOR EXAMPLE, IT WOULD BE BAD IF WE STARTED
	// RENDERING THE GAME AND 1/2 WAY THROUGH, THE GAME DATA CHANGED.
	// THAT MAY CAUSE BIG PROBLEMS. EACH THREAD WILL NEED TO LOCK
	// THE DATA BEFORE EACH USE AND THEN UNLOCK WHEN DONE WITH IT.
	protected ReentrantLock dataLock;
	
	// EVERY GAME WILL HAVE A SINGLE CANVAS INSIDE
	// OUR WINDOW. WE CAN PAINT AND HANDLE EVENTS
	// FOR BUTTONS OURSELVES
	protected Stage primaryStage;
//        protected Canvas canvas;
        
        public void setPrimaryStage(Stage primaryStage){
            this.primaryStage = primaryStage;
        }
        
        
//        protected Pane menuPane;
//        protected Pane playPane;
//        protected Pane winPane;
//        protected Pane lossPane;
//        protected AnchorPane anchorPane;
        
//        public Pane getAnchorPane(){
//            return this.anchorPane;
//        }
//        public Pane getMainPane(){
//            return this.menuPane;
//        }
//        
//        public Pane getPlayPane(){
//            return this.playPane;
//        }
//        
//        public Pane getWinPane(){
//            return this.winPane;
//        }
//        
//        public Pane getlossPane(){
//            return this.lossPane;
//        }
        
        
        
//        protected JFrame window;
//	protected JPanel canvas;

	// HERE ARE OUR GUI COMPONENTS. NOTE WE ARE NOT
	// USING SWING COMPONENTS (except JFrame and JPanel),
	// WE ARE MANAGING THE BUTTONS AND OTHER DISPLAY
	// ITEMS OURSLEVES. TreeMap IS SIMPLY A BINARY SEARCH
	// TREE WHERE THE String PARAMETER SPECIFIES THE ID
	// USED FOR KEEPING THE CONTROLS IN SORTED ORDER.
	// ONE HAS TO UNIQUELY NAME EACH CONTROL THAT GOES
	// IN THESE DATA STRUCTURES
	protected TreeMap<String, SpriteFx> guiButtons;
	protected TreeMap<String, SpriteFx> guiDecor;

	// WE WILL HAVE A TIMER RUNNING IN ANOTHER THREAD THAT
	// EVERY frameDuration AMOUNT OF TIME WILL UPDATE THE
	// GAME AND THEN RENDER. frameDuration IS THE NUMBER
	// OF MILLISECONDS IT TAKES PER FRAME, SO WE CAN
	// CALCULATE IT BY DIVIDING 1000 (MILLISECONDS IN 1 SEC.)
	// BY THE FRAME RATE, i.e. framesPerSecond
	// NOTE THAT THE GameTimerTask WILL HAVE THE CUSTOM
	// BEHAVIOUR PERFORMED EACH FRAME BY THE GAME
	protected Timer gameTimer;
	protected MiniGameTimerTaskFx gameTimerTask;
	protected int framesPerSecond;
	protected int frameDuration;
	
	// THESE VARIABLES STORE THE PIXEL DISTANCES FROM THE EDGES
	// OF THE CANVAS WHERE THE GAME WILL BE PLAYED. BY SETTING
	// THESE VALUES TO 0, THE FULL CANVAS WOULD BE THE PLAY AREA.
	// THESE ARE LEFT protected, SO A SUB CLASS, WHICH REPRESENTS
	// A CUSTOM GAME COULD CHANGE THESE AS THE DEVELOPER SEES FIT.
	protected float boundaryLeft = 0;
	protected float boundaryRight = 0;
	protected float boundaryTop = 0;
	protected float boundaryBottom = 0;
        	
	// WE'LL MANAGE THE EVENTS IN A SLIGHTLY DIFFERENT WAY
	// THIS TIME SINCE WE ARE RENDERING ALL OUR OWN
	// BUTTONS AND BECAUSE WE HAVE TO WORRY ABOUT 2 THREADS.
	// THIS OBJECT WILL RELAY BOTH MOUSE INTERACTIONS AND
	// KEYBOARD INTERACTIONS TO THE CUSTOM HANDLER, MAKING
	// SURE TO AVOID RACE CONDITIONS
	protected MiniGameEventRelayerFx gamh;
        
        private static Timeline gameLoop;
        
       
//        public MiniGameFx(){}
        public MiniGameFx(String appTitle, int initFramesPerSecond, Stage primaryStage){
            
            this.primaryStage = primaryStage;
            
//            menuPane = new Pane();
//            playPane = new Pane();
//            winPane = new Pane();
//            lossPane = new Pane();
            
//            anchorPane = new AnchorPane();
//            menuPane.getChildren().add(anchorPane);
            
            name = appTitle;            
            framesPerSecond = initFramesPerSecond;
            Scene s;// = new Scene(menuPane, 0 , 0, Color.GREEN);
            
            
            
            
            
            // CALCULATE THE TIME EACH FRAME SHOULD TAKE
            frameDuration = 1000/framesPerSecond;
//            buildAndSetGameLoop();

            // CONSTRUCT OUR LOCK, WHICH WILL MAKE SURE
            // WE ARE NOT UPDATING THE GAME DATA SIMULATEOUSLY
            // IN TWO DIFFERENT THREADS
            dataLock = new ReentrantLock();
            
//            data = new MiniGameDataModelFx();

            // AND NOW SETUP THE FULL APP. NOTE THAT SOME
            // OF THESE METHODS MUST BE CUSTOMLY PROVIDED FOR
            // EACH GAME IMPLEMENTATION
            initWindow();
            initData();
            s = initSetupScene();
            primaryStage.setScene(s);
            initGUI();
            initHandlers();
            MiniGameLooper mg = new MiniGameLooper(this);
//            initTimer();

            // LET THE USER START THE GAME ON DEMAND
            data.setGameState(MiniGameStateFx.NOT_STARTED);
//            primaryStage.show();
        }
        
        public abstract Scene initSetupScene();
        
//        protected final void buildAndSetGameLoop() {
//
//            final Duration oneFrameAmt = Duration.millis(1000 / framesPerSecond);
//            final KeyFrame oneFrame = new KeyFrame(oneFrameAmt,
//                    new EventHandler<ActionEvent>() {
//                @Override
//                public void handle(javafx.event.ActionEvent event) {
//
////                    if(data.getGameState().equals(MiniGameStateFx.IN_PROGRESS)){
//                        updateGUI();
////                    }
////                    else{
////                        removeHandlers();
////                    }
//                }
//            }); // oneFrame
//
//            // sets the game world's game loop (Timeline)
//            setGameLoop(TimelineBuilder.create()
//                    .cycleCount(Animation.INDEFINITE)
//                    .keyFrames(oneFrame)
//                    .build());
//        }
//        protected void setGameLoop(Timeline gameLoop) {
//            this.gameLoop = gameLoop;
//        }
        
        public static void setGameLoop(Timeline tl){
            gameLoop = tl;
        }
	
        // ACCESSOR METHODS
		// getDataModel
		// getFrameRate
		// getGUIButtons
		// getGUIDecor
		// getBoundaryLeft
		// getBoundaryRight
		// getBoundaryTop
		// getBoundaryBottom
		// getCanvas
	
	/**
	 * For accessing the game data.
	 * 
	 * @return the GameDataModel that stores all the game data.
	 */
	public MiniGameDataModelFx getDataModel() { return data; }
	
	/**
	 * For accessing the frame rate.
	 * 
	 * @return the frame rate, in frames per second of this application.
	 */
	public int getFrameRate()
	{
		return framesPerSecond;
	}

	/**
	 * For accessing the game GUI buttons.
	 * 
	 * @return the data structure containing all the game buttons.
	 */
	public TreeMap<String, SpriteFx> getGUIButtons() { return guiButtons; }
	
	/**
	 * For accessing the game GUI decor.
	 * 
	 * @return the data structure containing all the game decor.
	 */
	public TreeMap<String, SpriteFx> getGUIDecor() { return guiDecor; }

	/**
	 * For accessing the distance from the left edge of
	 * the game canvas that will be part of the playing game area.
	 * 
	 * @return the left boundary in pixels of the playing game area.
	 */
	public float getBoundaryLeft() { return boundaryLeft; }
	
	/**
	 * For accessing the distance from the left edge of the
	 * game canvas that represents the right edge of the playing
	 * game area.
	 * 
	 * @return the right boundary in pixels of the playing game area.
	 */
	public float getBoundaryRight() { return boundaryRight; }
	
	/**
	 * For accessing the distance from the top edge of
	 * the game canvas that will be part of the playing game area.
	 * 
	 * @return the top boundary in pixels of the playing game area.
	 */
	public float getBoundaryTop() { return boundaryTop; }
	
	/**
	 * For accessing the distance from the top edge of the
	 * game canvas that represents the bottom edge of the playing
	 * game area.
	 * 
	 * @return the bottom boundary in pixels of the playing game area.
	 */
	public float getBoundaryBottom() { return boundaryBottom; }

	/**
	 * For accessing the canvas, which is the JPanel that serves
	 * as the game rendering surface and mouse event source.
	 */
	public Stage getStage() { return primaryStage; }
        
//        public Canvas getCanvas() { return canvas; }
	
	// INITIALIZATION METHODS - NOTE THAT METHODS ARE MADE private
	// IN PART TO REMOVE THE TEMPTATION TO OVERRIDE THEM
		// initWindow
		// initGUI
		// initHandler

	/**
	 * Initializes our GUI's window. Note that this does
	 * not initialize the components inside, or the
	 * event handlers.
	 */
	private void initWindow()
	{
		// CONSTRUCT OUR WINDOW
//		primaryStage = new Stage();
//                System.out.println("name: " + name);
                primaryStage.setTitle(name);
                System.out.println("MiniGameFx initWindow - "
                        + "Missing setExtendedState "
                        + "- unsure if needed");
                primaryStage.setResizable(false);
                
//                window = new JFrame(name);
		
		// NOTE THAT THE WINDOW WILL BE RESIZED LATER
//		window.setExtendedState(JFrame.MAXIMIZED_BOTH);
//		window.setResizable(false);
		
		// THIS IS JUST A SIMPLE LITTLE GAME THAT DOESN'T
		// SAVE ANY DATA, SO WE'LL JUST KILL THE APP WHEN
		// THE USER PRESSES THE X
//		window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}	

	/**
	 * This should initialize and setup all GUI components. Note
	 * that it will invoke the custom-implemented initGUIControls
	 * method, which the game developer must provide to setup
	 * buttons.
	 */
	private void initGUI()
	{
		// INITIALIZE OUR GUI DATA STRUCTURES
		guiButtons = new TreeMap<String, SpriteFx>();
		guiDecor = new TreeMap<String, SpriteFx>();
	
		// WE'LL LAYOUT EVERYTHING USING PIXEL COORDINATES
		
//                window.setLayout(null);
                
		// GUI CONTROLS ARE SETUP BY THE GAME DEVELOPER
		// USING THIS FRAMEWORK
		initGUIControls();

		// ULTIMATELY, EVERYTHING SHOULD BE INSIDE THE CANVAS
//		window.add(canvas);
//		canvas.setBounds(0,0,data.getGameWidth(), data.getGameHeight());
//                System.out.println("gameheight: " + data);//.getGameHeight());
                primaryStage.setMaxHeight(data.getGameHeight());
                primaryStage.setMaxWidth(data.getGameWidth());
                primaryStage.setX(0);
                primaryStage.setY(0);
	}
	
	/**
	 * Sets up the event handler mechanics, including invoking
	 * the initGUIHandlers method, which would be provided by
	 * the game developer using this framework, and would
	 * presumably be different for each game.
	 */
	private void initHandlers()
	{
		// SETUP THE LOW-LEVEL HANDLER WHO WILL
		// RELAY EVERYTHING
            System.out.println("MinigameFX initHandlers()"
                    + " - Handlers not done");
		MiniGameEventRelayerFx mger = new MiniGameEventRelayerFx(this);
                Canvas canvas = ((MiniGameGroup)this.getStage().getScene().getRoot()).getCanvas();
		((MiniGameGroup)this.getStage().getScene().getRoot()).setOnMouseClicked(mger);
		((MiniGameGroup)this.getStage().getScene().getRoot()).setOnMouseClicked(mger);
                ((MiniGameGroup)this.getStage().getScene().getRoot()).setOnMouseDragEntered(mger);
                ((MiniGameGroup)this.getStage().getScene().getRoot()).setOnMouseDragExited(mger);
                ((MiniGameGroup)this.getStage().getScene().getRoot()).setOnMouseDragOver(mger);
                ((MiniGameGroup)this.getStage().getScene().getRoot()).setOnMouseDragReleased(mger);
                ((MiniGameGroup)this.getStage().getScene().getRoot()).setOnMouseDragged(mger);
                ((MiniGameGroup)this.getStage().getScene().getRoot()).setOnMouseEntered(mger);
                ((MiniGameGroup)this.getStage().getScene().getRoot()).setOnMouseExited(mger);
                ((MiniGameGroup)this.getStage().getScene().getRoot()).setOnMouseMoved(mger);
                ((MiniGameGroup)this.getStage().getScene().getRoot()).setOnMousePressed(mger);
                ((MiniGameGroup)this.getStage().getScene().getRoot()).setOnMouseReleased(mger);
//		this.setFocusable(true);
//		canvas.setOnMouseClicked(mger);
//                canvas.setOnMouseDragEntered(mger);
//                canvas.setOnMouseDragExited(mger);
//                canvas.setOnMouseDragOver(mger);
//                canvas.setOnMouseDragReleased(mger);
//                canvas.setOnMouseDragged(mger);
//                canvas.setOnMouseEntered(mger);
//                canvas.setOnMouseExited(mger);
//                canvas.setOnMouseMoved(mger);
//                canvas.setOnMousePressed(mger);
//                canvas.setOnMouseReleased(mger);
////		this.setFocusable(true);
//		window.addKeyListener(mger);
//		canvas.addKeyListener(mger);
//	
		// AND NOW LET THE GAME DEVELOPER PROVIDE
		// CUSTOM HANDLERS
		initGUIHandlers();
	}

	/**
	 * Sets up the timer, which will run the game updates
	 * and rendering on a fixed-interval schedule.
	 */
	public void initTimer()
	{
		gameTimerTask = new MiniGameTimerTaskFx(this);
		dataLock = new ReentrantLock();
		gameTimer = new Timer();
		gameTimer.scheduleAtFixedRate(gameTimerTask, 100, frameDuration);
	}	

	
	
	// METHODS FOR RUNNING THE GAME, PROVIDING THE MECHANICS
	// OF THESE APPLICATIONS. NOTE THAT THE DEVELOPER USING
	// THIS FRAMEWORK NEED NOT EVEN KNOW ABOUT THESE METHODS,
	// JUST HOW TO PLUG INTO THEM THE SAME WAY YOU DON'T KNOW
	// ABOUT ALL THE INTERNAL WORKINGS OF SWING
		// beginUsingData
		// endUsingData
		// killApplication
		// loadImage
		// loadImageWithColorKey
		// processButtonPress
		// startGame
		// update
	
	/**
	 * This method locks access to the game data for the thread
	 * that invokes this method. All other threads will be locked
	 * out upon their own call to this method and will be forced
	 * to wait until this thread ends its use.
	 */
	public void beginUsingData()
	{
		dataLock.lock();
	}

	/**
	 * This method frees access to the game data for the thread
	 * that invokes this method. This will result in notifying any
	 * waiting thread that it may proceed.
	 */
	public void endUsingData()
	{
		dataLock.unlock();
	}

	/**
	 * Call this method to kill the application associated with this object,
	 * including closing the window.
	 */
	public void killApplication()
	{
		primaryStage.close();//.setVisible(false);
		System.exit(0);
	}

//	/**
//	 * Loads an image using the fileName as the full path,
//	 * returning the constructed and completely loaded Image.
//	 * 
//	 * @param fileName full path and name of the location of
//	 * the image file to be loaded.
//	 * 
//	 * @return the loaded Image, with all data fully loaded.
//	 */
//	public Image loadImage(String fileName) 
//	{
////            System.out.println("filename: " + fileName);
////                System.out.println("\tloadImage(String fileName)");
//                File file = new File(fileName);
//                if(file.exists()){
//                    fileName = file.getAbsolutePath();
//                }
////                System.out.println("filename: " + fileName);
//                Image img = null;
//                try{
////                System.out.println(file.toURI().toURL().toExternalForm());
//                 img = new Image(file.toURI().toURL().toExternalForm());
//                 
//                }catch(Exception e){
//                    System.out.println("image could not be loaded ");
//                    e.printStackTrace();
//                }
//                return img;
//		// LOAD THE IMAGE
////		Toolkit tk = Toolkit.getDefaultToolkit();
////		Image img = tk.createImage(fileName);
////
////                // AND WAIT FOR IT TO BE FULLY IN MEMORY BEFORE RETURNING IT
////		MediaTracker tracker = new MediaTracker(window);
////		tracker.addImage(img, 0);
////		try { tracker.waitForID(0); }
////		catch(InterruptedException ie) {System.out.println("MT INTERRUPTED");}
//// 
////                // WE'LL USE BUFFERED IMAGES
////                BufferedImage imageToLoad = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
////		Graphics g = imageToLoad.getGraphics();
////		g.drawImage(img, 0, 0, null);
////
////		return imageToLoad;
//	}
//        
//        /**
//         * loads image with custom width and height
//         * uses default non-image-smoothing algorithm
//         * @param fileName
//         * @param width
//         * @param height
//         * @return 
//         */
//        public Image loadImage(String fileName, int width, int height){
//            
//            //TODO must implement same funtionality as other loadImage function
//            System.out.println("\tloadImage(String fileName, int width, int height)");
//            File file = new File(fileName);
//            if(file.exists())
//                fileName = file.getAbsolutePath();
//            Image img = null;
//            try{
//                img= new Image(file.toURI().toURL().toExternalForm(), width, height, false, false, false);
//            }catch(MalformedURLException e){
//                e.printStackTrace();
//            }
////            Image img = new Image(fileName, width, height, false, false, false);
//            return img;
//            
//        }
//        /**
//         * load image with custom width and height
//         * with intent of being used as background
//         * uses default non-image-smoothing-algorithm
//         * @param fileName
//         * @param width
//         * @param height
//         * @return 
//         */
//        public Image loadImageAsBackground(String fileName, int width, int height){
//            
//            //TODO must implement same funtionality as other loadImage function
////            System.out.println("\tloadImageAsBackground(String fileName, int width, int height)");
//            //TODO this function might not be necessary 
//            Image img = new Image(fileName, width, height, false, false, true);
//            return img;
//        }
//
//	/**
//	 * Loads an image using the fileName as the full path,
//	 * returning the constructed and completely loaded Image. Note
//	 * that all pixels with the colorKey value will be made
//	 * transparent by setting their alpha values to 0.
//	 * 
//	 * @param fileName full path and name of the location of
//	 * the image file to be loaded.
//	 * 
//	 * @return the loaded Image, with all data fully loaded and
//	 * with colorKey pixels transparent.
//	 */
//	public Image loadImageWithColorKey(String fileName, int width, int height, Color colorKey)
//	{
////            System.out.println("\tloadImageWithColorKey(String fileName, int width, int height, Color colorKey) test?");
//            //TODO must implement with same Functionality as original method
////            System.out.println("mingameFx - incomplete loadImagewithColorKey");
////            System.out.println("file path; " + fileName);
//            Image img = null;
//            ImageView iv = new ImageView();
//            File file = new File(fileName); 
//            try{
//                if(width == 0 && height == 0){
//                    img= new Image(file.toURI().toURL().toExternalForm());
//                }else{
////                    img= new Image(getClass().getResource(fileName).toExternalForm(), width, height, false, false);
//                    img= new Image(file.toURI().toURL().toExternalForm(), width, height, false, false);
//                }
//            }catch(MalformedURLException e){
//                e.printStackTrace();
//                System.exit(0);
//            }
//           
//            WritableImage wi = new WritableImage((int)img.getWidth(), (int)img.getHeight());
//            PixelWriter px = wi.getPixelWriter();
//            iv.setImage(img);
//            
//            PixelReader pr = iv.getImage().getPixelReader();
//            
//            for(int x = 0; x < img.getWidth(); x ++){
//                for(int y = 0; y < img.getHeight(); y ++){
//                    Color c = pr.getColor(x, y);
//                    Color temp;
//                    if(c.getBlue() == colorKey.getBlue() && c.getRed() == colorKey.getRed() && c.getGreen() == colorKey.getGreen()){
//                        temp = new Color(c.getRed(), c.getBlue(), c.getGreen(), 0);
////                        System.out.println("match at pixle (" + x +", " + y + ")" );
//                    }else{
//                        temp = new Color(c.getRed(), c.getBlue(), c.getGreen(), 1);
//                    }
//                    px.setColor(x, y, temp);
//                        
//                }
//            }
//            return wi;
//            
//                // LOAD THE IMAGE
////		Toolkit tk = Toolkit.getToolkit();
////                
////                Media m = new Media(fileName);
////                
////                Image img = tk.createImage(fileName);
////                System.out.println(fileName);
//		
////		// WAIT FOR IT TO BE FULLY IN MEMORY
////		MediaTracker tracker = new MediaTracker(window);
////		tracker.addImage(img, 0);
////		try { tracker.waitForAll(); }
////		catch(Exception e ) { e.printStackTrace(); }
////		
////		// WE'LL PUT IT IN A BufferedImage OBJECT SINCE
////		// THAT TYPE OF IMAGE PROVIDES US ACCESS TO
////		// ALL OF THE RAW DATA
////		BufferedImage imageToLoad = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
////		Graphics g = imageToLoad.getGraphics();
////		g.drawImage(img, 0,	0,	null);
////		
////		// NOW MAKE ALL PIXELS WITH COLOR (64, 224, 224) TRANSPARENT
////		WritableRaster raster = imageToLoad.getRaster();
////		int[] dummy = null;
////		for (int i = 0; i < raster.getWidth(); i++)
////		{
////			for (int j = 0; j < raster.getHeight(); j++)
////			{
////				int[] pixel = raster.getPixel(i, j, dummy);
////				if ((pixel[0] == colorKey.getRed())
////					&& (pixel[1] == colorKey.getGreen())
////					&& (pixel[2] == colorKey.getBlue()))
////				{
////					pixel[3] = 0;
////					raster.setPixel(i, j, pixel);
////				}
////			}
////		}		
////		return imageToLoad;
//	
//        }
	/**
	 * When invoked, this method results in each button
	 * in the GUI testing to see if the x, y coordinates
	 * are inside its bounds. If they are, the button's
	 * actionPerformed method is invoked and the appropriate
	 * event response is executed.
	 * 
	 * @param x the x coordinate on the canvas of the mouse press
	 * 
	 * @param y the y coordinate on the canvas of the mouse press
	 * 
	 * @return true if the mouse press resulted in a button's
	 * event handler being executed, false otherwise. This is important
	 * because if false is returned, other game logic should proceed.
	 */
	public boolean processButtonPress(int x, int y)
	{
            System.out.println("MiniGamefx - processButtonPress");
//            return false;
		boolean buttonClickPerformed = false;
		
		// TEST EACH BUTTON
		for (SpriteFx s : guiButtons.values())
		{
			// THIS METHOD WILL INVOKE actionPeformed WHEN NEEDED
			buttonClickPerformed = s.testForClick(this, x, y);
			
			// ONLY EXECUTE THE FIRST ONE, SINCE BUTTONS
			// SHOULD NOT OVERLAP
			if (buttonClickPerformed){
			System.out.println("MiniGameFx - processButtonPress");	
                            return true;    
                        }
		}
		return false;
	}
	
	/**
	 * Displays the window, allowing the MiniGame application
	 * to start accepting user input and allow the user to
	 * actually play the game.
	 */
	public void startGame(Stage primaryStage) 
	{
            
		// DISPLAY THE WINDOW
//		window.setVisible(true);
//		this.primaryStage = primaryStage;
		// LET'S NOW CORRECT THE WINDOW SIZE. THE REASON WE DO THIS
		// IS BECAUSE WE'RE USING null LAYOUT MANAGER, SO WE NEED TO
		// SIZE STUFF OURSELVES, AND WE WANT THE WINDOW SIZE TO BE THE
		// SIZE OF THE CANVAS + THE BORDER OF THE WINDOW, WHICH WOULD
		// INCLUDE THE TITLE BAR. THIS IS CALLED THE WINDOW'S INSETS
//		Insets insets = window.getInsets();
//		int correctedWidth  = data.getGameWidth() + insets.left + insets.right;
//		int correctedHeight = data.getGameHeight() + insets.top + insets.bottom;
//		window.setSize(correctedWidth, correctedHeight);
            System.out.println("MiniGameFx - start - must figure out way to get useable"
                    + " area of primaryStage");
            System.out.println("." + data.getGameHeight() + ".");
            
            
//            this.primaryStage.setWidth(data.getGameWidth());
//            this.primaryStage.setHeight(data.getGameHeight());
//            Scene scene = new Scene(menuPane, 0 , 0, Color.GREEN);
//            this.primaryStage.setScene(scene);
            this.primaryStage.show();
//            data.beginGame();
//            new AnimationTimer() {
//                @Override
//                public void handle(long now) {
//                    gameTimerTask.run();
//                }
//            }.start();

	}
        public void beginGame(){
            gameLoop.play();
        }
        public void endGame(){
            gameLoop.stop();
        }
        public void pauseGame(){
            gameLoop.pause();
        }

	/**
	 * This method is called once per frame and updates and 
	 * renders everything in the game including the gui.
	 */
	public void update()
	{
		// WE ONLY PERFORM GAME LOGIC
		// IF THE GAME IS UNDERWAY
		if (data.inProgress())
		{
			data.updateDebugText(this);
			if (!data.isPaused())
				data.updateAll(this);
		}
		// WE ALWAYS HAVE TO WORRY ABOUT UPDATING THE GUI
		updateGUI();
	}	
	
	// ABSTRACT METHODS - GAME-SPECIFIC IMPLEMENTATIONS REQUIRED
		// gameWon
		// gameLost
		// initData
		// initGUIControls
		// initGUIHandlers
		// reset
		// updateGUI
	
	/**
	 * Initializes the game data used by the application. Note
	 * that it is this method's obligation to construct and set
	 * this Game's custom GameDataModel object as well as any
	 * other needed game objects.
	 */
	public abstract void initData();
	
	/**
	 * Initializes the game controls, like buttons, used by
	 * the game application.
	 */
	public abstract void initGUIControls();
	
	/**
	 * Initializes the game event handlers for things like
	 * game gui buttons.
	 */
	public abstract void initGUIHandlers();
	
	/**
	 * Invoked when a new game is started, it resets all relevant
	 * game data and gui control states. 
	 */
	public abstract void reset();
	
	/**
	 * Updates the state of all gui controls according to the 
	 * current game conditions.
	 */
	public abstract void updateGUI();
//        public abstract void removeHandlers();
}
